--Matt Forbes
--11/21/09
--Assignment 4 helper package

with ada.text_io;
with ada.strings.unbounded;
use ada.strings.Unbounded;

package body sudoku is

   package t_io renames ada.text_io;

   --global variables
   board     : boardArray;
   initBoard : boardArray;
   rows      : rowArray;
   columns   : columnArray;
   subs      : subArray;
   puzzleFile : t_io.File_Type;


   procedure doPuzzle(puzzleFile : t_io.File_Type) is
   begin
      getPuzzle(puzzleFile);
      for i in 1..10 loop
         updatePuzzle;
         fillPuzzle;
         crossHatch;
         if isSolved then
            exit;
         end if;
      end loop;
      printSolution;
   end doPuzzle;

   procedure updatePuzzle is
   begin
      for j in boardSize loop
         for i in boardSize loop
            if rows(i).solved = false or columns(j).solved = false then
               checkRowsAndColumns(i, j);
            end if;
            if subs( board(i, j).subNum ).solved = false then
               checkSub(i, j);
            end if;
         end loop;
      end loop;
   end updatePuzzle;

   procedure checkRowsAndColumns(x : in Integer; y : in Integer) is
      value : Integer;
   begin
      if board(x, y).solved /= true then
         for q in boardSize loop
            if q /= y then
               if board(x, q).solved = true then
                  value := Character'pos(board(x, q).value) - Character'pos('0');
                  board(x, y).possible(value) := false;
               end if;
            end if;
            if q /= x then
               if board(q, y).solved = true then
                  value := Character'pos(board(q, y).value) - Character'pos('0');
                  board(x, y).possible(value) := false;
               end if;
            end if;
         end loop;
         checkSquare(x, y);
      end if;
   end checkRowsAndColumns;

   procedure checkSub(x : in Integer; y : in Integer) is
      value : Integer;
      subIndex : Integer;
      tX, tY : Integer;
   begin
      subIndex := board(x, y).subNum;
      if board(x, y).solved /= true then
         tX := subs(subIndex).x;
         tY := subs(subIndex).y;
         for j in tY..(tY+2) loop
            for i in tX..(tX+2) loop
               if i /= x and j /= y and board(i, j).solved then
                  value := Character'pos(board(i,j).value) - Character'pos('0');
                  board(x, y).possible(value) := false;
               end if;
            end loop;
         end loop;
         checkSquare(x, y);
      end if;
   end checkSub;

   procedure checkSquare(x : in Integer; y : in Integer) is
      possible, value : Integer;
   begin
      if board(x, y).solved = false then
         possible := 0;
         for i in boardSize loop
            if board(x, y).possible(i) = true then
               possible := possible + 1;
               value := i;
            end if;
         end loop;
         if possible = 1 then
            solveSquare(x, y, value);
         end if;
      end if;
   end checkSquare;

   procedure fillPuzzle is
   begin
      for i in boardSize loop
         for j in boardSize loop
            if rows(i).possible(j) = true then
               fillRow(i, j);
               null;
            end if;
            if columns(i).possible(j) = true then
               fillColumn(i, j);
               null;
            end if;
            if subs(i).possible(j) = true then
               fillSub(i, j);
            end if;
         end loop;
      end loop;
   end fillPuzzle;

   procedure fillRow(rowNum : in Integer; value : in Integer) is
      possible, position : Integer;
   begin
      possible := 0;
      for i in boardSize loop
         if board(i, rowNum).solved = false and board(i, rowNum).possible(value) = true then
            possible := possible + 1;
            position := i;
         end if;
      end loop;
      if possible = 1 then
         solveSquare(position, rowNum, value);
      end if;
   end fillRow;

   procedure fillColumn(colNum : in Integer; value : in Integer) is
      possible, position : Integer;
   begin
      possible := 0;
      for j in boardSize loop
         if board(colNum, j).solved = false and board(colNum, j).possible(value) = true then
            possible := possible + 1;
            position := j;
         end if;
      end loop;
      if possible = 1 then
         solveSquare(colNum, position, value);
      end if;
   end fillColumn;

   procedure fillSub(subNum : in Integer; value : in Integer) is
      posX, posY : Integer;
      possible : Integer;
   begin
      possible := 0;
      for j in subs(subNum).y .. (subs(subNum).y + 2) loop
         for i in subs(subNum).x .. (subs(subNum).x + 2) loop
            if board(i, j).solved = false and board(i, j).possible(value) = true then
               possible := possible + 1;
               posX := i;
               posY := j;
            end if;
         end loop;
      end loop;
      if possible = 1 then
         solveSquare(posX, posY, value);
      end if;
   end fillSub;

   procedure crossHatch is
   begin
      for i in boardSize loop
         crossHatchRow(i);
         crossHatchColumn(i);
      end loop;
   end crossHatch;

   procedure crossHatchRow(subNum : in Integer) is
      numPossible : Integer;
      possibilities : possibleArray;
   begin
      for y in subs(subNum).y .. (subs(subNum).y + 2) loop

         numPossible := 0;
         possibilities := rows(y).possible;

         for i in boardSize loop
            if i < subs(subNum).x or i > (subs(subNum).x+2) then
               for q in boardSize loop
                  if possibilities(q) = true then
                     if board(i, y).possible(q) = true then
                        possibilities(q) := false;
                     end if;
                  end if;
               end loop;
            end if;
         end loop;

         for q in boardSize loop
            if possibilities(q) = true then
               numPossible := numPossible + 1;
            end if;
         end loop;

         if numPossible <= 3 then
            for j in subs(subNum).y .. (subs(subNum).y+2) loop
               for i in subs(subNum).x .. (subs(subNum).x+2) loop
                  if j /= y then
                     for q in boardSize loop
                        if possibilities(q) = true then
                           board(i, j).possible(q) := false;
                        end if;
                     end loop;
                  end if;
               end loop;
            end loop;
         end if;
      end loop;




   end crossHatchRow;

   procedure crossHatchColumn(subNum : in Integer) is
      numPossible : Integer;
      possibilities : possibleArray;
   begin
      for x in subs(subNum).x .. (subs(subNum).x+2) loop

         numPossible := 0;
         possibilities := columns(x).possible;

         for j in boardSize loop
            if j < subs(subNum).y or j > (subs(subNum).y + 2) then
               for q in boardSize loop
                  if possibilities(q) = true then
                     if board(x, j).possible(q) = true then
                        possibilities(q) := false;
                     end if;
                  end if;
               end loop;
            end if;
         end loop;

         for q in boardSize loop
            if possibilities(q) = true then
               numPossible := numPossible + 1;
            end if;
         end loop;

         if numPossible <= 3 then
            for j in subs(subNum).y .. (subs(subNum).y + 2) loop
               for i in subs(subNum).x .. (subs(subNum).x + 2) loop
                  if x /= i then
                     for q in boardSize loop
                        if possibilities(q) = true then
                           board(i, j).possible(q) := false;
                        end if;
                     end loop;
                  end if;
               end loop;
            end loop;
         end if;

      end loop;
   end crossHatchColumn;

   procedure solveSquare(x : in Integer; y : in Integer; value : in Integer) is
   begin
      board(x, y).solved := true;
      board(x, y).value := Character'val( Character'pos('0') + value );
      for i in boardSize loop
         if i/= value then
            board(x, y).possible(i) := false;
         end if;
      end loop;
      solveBoardObject(item => rows(y), value => value);
      solveBoardObject(item => columns(x), value => value);
      solveBoardObject(item => subs (board(x, y).subNum), value => value);
   end solveSquare;

   procedure solveBoardObject(item : in out boardObject'Class; value : in Integer) is
      isSolved : Boolean;
   begin
      isSolved := true;
      t_io.put_line(integer'image(value));
      item.possible(value) := false;
      for i in boardSize loop
         if item.possible(i) = true then
            isSolved := false;
            exit;
         end if;
      end loop;
      if isSolved then
         item.solved := true;
      end if;
   end solveBoardObject;

   procedure getPuzzle(puzzleFile : t_io.file_type) is
      c : Character;
      value : Integer;
      subColumn, subRow : Integer;
   begin
      reset(rows);
      reset(columns);
      reset(subs);
      subRow := 0;
      subColumn := 0;
      for j in boardSize loop
         for i in boardSize loop
            loop
               t_io.get(file => puzzleFile, item => c);
               exit when c /= ' ';
            end loop;
            initBoard(i, j) := emptySquare(c);
            board(i, j) := emptySquare(c);
            board(i, j).subNum := (subRow/3)*3 + subColumn/3 + 1;
            subColumn := subColumn + 1;
            if c /= '.' then
               value := Character'pos(c) - Character'pos('0');
               solveSquare(i, j, value);
            end if;
         end loop;
         subRow := subRow + 1;
         subColumn := 0;
      end loop;
   end getPuzzle;

   procedure printSolution is
      count : Integer;
   begin
      count := 0;
      for j in boardSize loop
         for i in boardSize loop
            t_io.put(initBoard(i, j).value);
            count := count + 1;
            if count rem 3 = 0 then
               t_io.put(" ");
            end if;
         end loop;
         t_io.put("          ");
         for i in boardSize loop
            t_io.put(board(i, j).value);
            count := count + 1;
            if count rem 3 = 0 then
               t_io.put(" ");
            end if;
         end loop;
         t_io.new_line;
      end loop;
      t_io.new_line;
   end printSolution;

   function isSolved return Boolean is
      solved : Boolean;
   begin
      solved := true;
      for j in boardSize loop
         for i in boardSize loop
            if board(i,j).solved = false then
               solved := false;
            end if;
         end loop;
      end loop;
      return solved;
   end isSolved;

   function emptySquare(value : in Character)  return square is
      aSquare : square;
   begin
      aSquare.solved := false;
      aSquare.value := value;
      for i in boardSize loop
         aSquare.possible(i) := true;
      end loop;
      return aSquare;
   end emptySquare;

   procedure loadPuzzleFile(fileName : unbounded_string) is
   begin
      t_io.Open(File => puzzleFile,
                Mode => t_io.in_file,
                Name => to_string(fileName));
   end loadPuzzleFile;

   procedure reset(item : in out rowArray) is
   begin
      for i in boardSize loop
         item(i).solved := false;
         reset(item(i).possible);
      end loop;
   end reset;

   procedure reset(item : in out columnArray) is
   begin
      for i in boardSize loop
         item(i).solved := false;
         reset(item(i).possible);
      end loop;
   end reset;

   procedure reset(item : in out subArray) is
      countX, countY: Integer;
   begin
      countX := 0;
      countY := 0;
      for i in boardSize loop
         item(i).solved := false;
         reset(item(i).possible);
         item(i).x := (countX mod 3) * 3 + 1;
         item(i).y := countY * 3 + 1;
         countX := countX + 1;
         if countX mod 3 = 0 then
            countY := countY + 1;
         end if;
      end loop;
   end reset;

   procedure reset(item : in out possibleArray) is
   begin
      for i in boardSize loop
         item(i) := true;
      end loop;
   end reset;

end sudoku;

